package com.openway.game.personage.impl;

import com.openway.game.Game;
import com.openway.game.field.Coordinate;
import com.openway.game.field.Direction;
import com.openway.game.PoliceStation;
import com.openway.game.personage.AbstractMovingGameObject;
import com.openway.game.personage.GameObject;

import java.util.*;

/**
 * The task of the policeman - take away the sleeping drunkards to the police station.
 *
 * @author Created: 01.03.12 23:34
 * @version $Date$ $Revision$
 */
public class Policeman extends AbstractMovingGameObject {

    private Drunkard goalDrunkard;  //drunkard, that policeman try to take away from field at this moment
    private boolean isDrunkardCaught;
    private boolean isInPoliceStation; //if policeman is free
    private Deque<Direction> bypass;   //bypass route, maybe empty

    public Policeman() {
        setInPoliceStation();
    }

    /**
     * This policeman at the station waiting for the new sleeping drunkards
     */
    private void setInPoliceStation() {
        goalDrunkard = null;
        isDrunkardCaught = false;
        isInPoliceStation = true;
        bypass = new LinkedList<Direction>();
    }

    @Override
    public boolean canMove() {
        return true;
    }

    @Override
    public void makeMove() {
        if (isInPoliceStation) { //Policeman is at the police station and waits for the sleeping drunkards
            goalDrunkard = Game.getInstance().getPoliceStation().getDrunkardToTakeAway();
            isDrunkardCaught = false;
        }

        if (goalDrunkard == null)
            return;

        if (isInPoliceStation) {
            exitFromStation();
            return;
        }

        Direction direction = getMoveDirection();
        if (direction == null) { //policeman is near the police station
            enterToStation();
            return;
        }

        boolean isSuccess = executeMove(direction);
        if (!isSuccess) {
            bypass(direction);         //generate bypass route
        }
    }

    /**
     * Return back to police station (with sleeping drunkard)
     */
    private void enterToStation() {
        Game.getInstance().getPlayingField().removeObject(this);
        setInPoliceStation();
    }

    /**
     * Go out from police station
     */
    private void exitFromStation() {
        GameObject objectInInitialCoordinate =
                Game.getInstance().getPlayingField().getObject(PoliceStation.INITIAL_POLICEMAN_COORDINATE);
        boolean canExit = handleCollisions(objectInInitialCoordinate);
        if (canExit) {
            Game.getInstance().getPlayingField().addObject(this, PoliceStation.INITIAL_POLICEMAN_COORDINATE);
            isInPoliceStation = false;
        }
    }

    /**
     * @return true if this move resulted in a change of drinker's position
     *         false otherwise.
     */
    private boolean executeMove(Direction direction) {
        Coordinate policemanCoordinate = Game.getInstance().getPlayingField().getObjectCoordinate(this);
        GameObject objectInTargetCoordinate = Game.getInstance().getPlayingField().getAdjacentObject(policemanCoordinate, direction);

        boolean canChangePosition = handleCollisions(objectInTargetCoordinate);
        if (canChangePosition) {
            Game.getInstance().getPlayingField().moveObject(this, direction);
            return true;
        }

        return false;
    }

    /**
     * Handling collision with other objects.
     * <p/>
     * This method is responsible for the compliance of game rule:
     * if policeman runs into any object, he doesn't move.
     *
     * @return true if policeman can change position, else otherwise.
     */
    private boolean handleCollisions(GameObject objectInTargetCoordinate) {
        if (objectInTargetCoordinate == null)
            return true;

        if (objectInTargetCoordinate instanceof Drunkard) {
            if (objectInTargetCoordinate.equals(goalDrunkard)) {         //catch drunkard
                Game.getInstance().getPlayingField().removeObject(goalDrunkard);
                isDrunkardCaught = true;
                return true;
            } else {
                return false;
            }
        }

        //check if runs into the post, lamp, drinker, bottle
        return !(objectInTargetCoordinate instanceof Post || objectInTargetCoordinate instanceof Lamp ||
                objectInTargetCoordinate instanceof Bottle);
    }

    /**
     * @return direction or null if policeman next moving is going to the police station
     */
    private Direction getMoveDirection() {
        if (!isDrunkardCaught) {     //go towards the drunkard
            return getDirectionToTarget(Game.getInstance().getPlayingField().getObjectCoordinate(goalDrunkard));
        } else {                    //go towards the police station
            return getDirectionToTarget(PoliceStation.INITIAL_POLICEMAN_COORDINATE);
        }
    }

    //This is not optimal algorithm
    private Direction getDirectionToTarget(Coordinate target) {
        if (bypass != null && !bypass.isEmpty()) {            //if bypass is not empty - use it
            Direction direction = bypass.pollLast();
            if (Game.getInstance().getPlayingField().isInsideDirection(this, direction))
                return direction;
            else
                return direction.getOppositeDirection();
        }

        Coordinate policemanCoordinate = Game.getInstance().getPlayingField().getObjectCoordinate(this);
        if (policemanCoordinate.getRow() < target.getRow()) {
            return Direction.DOWN;
        }
        if (policemanCoordinate.getRow() > target.getRow()) {
            return Direction.UP;
        }
        if (policemanCoordinate.getColumn() < target.getColumn()) {
            return Direction.RIGHT;
        }
        if (policemanCoordinate.getColumn() > target.getColumn()) {
            return Direction.LEFT;
        }
        return null;
    }

    private void bypass(Direction failDirection) {
        List<Direction> possibleDirections = new ArrayList<Direction>();
        switch (failDirection) {
            case LEFT:
            case RIGHT:
                if (Game.getInstance().getPlayingField().isInsideDirection(this, Direction.UP))
                    possibleDirections.add(Direction.UP);
                if (Game.getInstance().getPlayingField().isInsideDirection(this, Direction.DOWN))
                    possibleDirections.add(Direction.DOWN);
                break;
            case UP:
            case DOWN:
                if (Game.getInstance().getPlayingField().isInsideDirection(this, Direction.RIGHT))
                    possibleDirections.add(Direction.RIGHT);
                if (Game.getInstance().getPlayingField().isInsideDirection(this, Direction.LEFT))
                    possibleDirections.add(Direction.LEFT);
        }

        Random random = new Random();
        int randomDirectionIndex = random.nextInt(possibleDirections.size());
        bypass.addLast(failDirection);
        bypass.addLast(possibleDirections.get(randomDirectionIndex));
    }

    @Override
    public String toString() {
        Coordinate coordinate = Game.getInstance().getPlayingField().getObjectCoordinate(this);
        return "Policeman{" +
                "id=" + getId() +
                ", coordinate=" + coordinate +
                '}';
    }
}
